package taskTracker;

import java.util.Hashtable;
import java.util.Vector;

/**
 * Holds a group of time entries.
 * Typically there will be one instant of this class per application.
 * However, this is not a must.
 * @author aseldawy, Mostafa Ali
 *
 */
public class TimeEntries {
	private static String ADD_EVENT = "add";
	private static String REMOVE_EVENT = "remove";
	private static String UPDATE_EVENT = "update";	
	
	private static TimeEntries instance;
	
	private Vector listeners;		//TOASK: What is this for? 
	
	/**All time entries added*/
	private Vector timeEntries;
	
	/**
	 * Returns an instance of TimeEntries.
	 * This method enforces singleton.
	 * @return
	 */
	public static synchronized TimeEntries getInstance(){
		if (instance == null) {
			instance = new TimeEntries();
		}
		return instance;
	}
	
	private TimeEntries() {
		this.timeEntries = new Vector();
		this.listeners = new Vector();
	}	

	/**
	 * Get all TimeEntries entered for the given task.
	 * @param task
	 * @return
	 */
	public Vector getEntriesForTask(Task task) {
		Vector taskEntries = new Vector();
		
		for(int i = 0; i < timeEntries.size(); i++){
			TimeEntry entry = (TimeEntry)timeEntries.elementAt(i);	
			
			if(entry.getTask() == task){
				taskEntries.addElement(entry);
			}
		}
		return taskEntries;
	}	
	
	/**
	 * Get all entries associated with the given project.
	 * This means all entries associated with any of its tasks.
	 * @param project
	 * @return
	 */
	public Vector getEntriesForProject(Project project){
		Vector taskEntries = new Vector();
		
		for(int i = 0; i < timeEntries.size(); i++){
			TimeEntry entry = (TimeEntry)timeEntries.elementAt(i);	
			
			if(entry.getTask().getParent() == project){
				taskEntries.addElement(entry);
			}
		}
		return taskEntries;
	}

	/**
	 * Calculates total time logged for the given task.
	 * @param task
	 * @return
	 */
	public long getTotalTimeForTask(Task task) {
		long result = 0;
		
		for(int i = 0; i < timeEntries.size(); i++){
			TimeEntry entry = (TimeEntry)timeEntries.elementAt(i);
			
			if(entry.getTask() == task){
				result += entry.getTotalTime();
			}
		}
		return result;
	}

	/**
	 * Calculates total time logged for Projects or Tasks tagged with the given tag.
	 * @param tag
	 * @return
	 */
	public long getTotalTimeForTag(Tag tag) {
		// taggedObjects are either TaskList or Task (not time entries)
		// For Projects, all tasks inside it are counted
		// For Tasks, this only task is counted
		// Take care because the same task may be repeated when it's tagged and its project is also tagged.
		// It should be counted only once
		Vector taggedObjects = tag.getTagged();
		long result = 0;
		
		// hash of the counted tasks so not to count a task twice
		Hashtable countedTasks = new Hashtable();
		
		for(int i = 0; i < taggedObjects.size(); i++){
			Object entry = (Object)taggedObjects.elementAt(i);						
			
			if (entry instanceof Task) {
				// make sure it's not counted twice
				if (!countedTasks.contains(entry)) {
					result += getTotalTimeForTask( (Task)entry);
					countedTasks.put(entry, null);
				}
			} else if(entry instanceof Project){
				Project project = (Project) entry;
				result += project.getTotalTime();
				
				// add all project tasks to counted list
				Vector tasks = project.getTasks();
				
				for(int j = 0; j < tasks.size(); j++){					
					Task task = (Task) tasks.elementAt(j);
					countedTasks.put(task, null);
				}				
			}
		}
		return result;
	}
	
	/**
	 * get all time entries for tags and projects tagged with the given tags
	 * @param tag
	 * @return
	 */
	public Vector getTimeEntriesforTags(Vector tags) {
		// taggedObjects are either TaskList or Task (not time entries)
		// For Projects, all tasks inside it are counted
		// For Tasks, this only task is counted
		// Take care because the same task may be repeated when it's tagged and its project is also tagged.
		// It should be counted only once		
		
		// get all tagged objects
		Vector allTaggedObjects = new Vector();
		for(int i = 0; i < tags.size(); i++){
			Tag tag = (Tag)tags.elementAt(i);
			Vector taggedObjects = tag.getTagged();
			
			for(int j = 0; j < taggedObjects.size(); j++){
				Object taggedObject = taggedObjects.elementAt(j);
				allTaggedObjects.addElement(taggedObject);				
			}
		}
		
		// get entries from tagged objects
		Vector allTimeEntries = new Vector();
		for(int i = 0; i < allTaggedObjects.size(); i++){
			Object taggedObject = allTaggedObjects.elementAt(i);
			
			Vector entries = new Vector();
			if(taggedObject instanceof Task){
				Task task = (Task) taggedObject;
				entries = getEntriesForTask(task);
			} else if(taggedObject instanceof Project){
				Project project = (Project) taggedObject;
				entries = getEntriesForProject(project);
			}
			
			for(int j = 0; j < entries.size(); j++){
				TimeEntry entry = (TimeEntry) entries.elementAt(j);
				
				// add each entry only one time
				if(allTimeEntries.contains(entry))
					continue;
				
				allTimeEntries.addElement(entry);
			}			
		}
		return allTimeEntries;		
	}
	
	/**
	 * Logs a time entry under the given task.
	 * If this time entry is adjacent or intersecting with another time entry, it is merged with it rather than creating a new entry. 
	 * @param task - the Task to log time
	 * @param from - start time
	 * @param to - end time
	 * @param comment - any comments associated with it
	 */
	public void logTime(Task task, long from, long to, String comment) {
		// get intersecting Time Entry with the given time for the given Task
		// and if one is found, just merge it with the given time span		
		Vector entries = getEntriesForTask(task);
		
		for(int i=0 ; i < entries.size(); i++){
			TimeEntry  entry = (TimeEntry) entries.elementAt(i);
			
			if(entry.instersectTimeSpan(from, to)){
				entry.mergeTimeSpan(from, to);
				fireTimeEntryAdded(entry, UPDATE_EVENT);
				return;
			}
		}
		
		// no intersecting TimeEntry is found, so just create a new one
		TimeEntry newTimeEntry = new TimeEntry(from, to, task, comment);
		
		timeEntries.addElement(newTimeEntry);
		fireTimeEntryAdded(newTimeEntry, ADD_EVENT);
	}

	/**
	 * Adds a TimeEntriesListener that is notified when a TimeEntry is created, updated or deleted.
	 * @param listener
	 */
	public void addTimeEntriesListener(TimeEntriesListener listener){
		if(listeners.indexOf(listener) < 0)
			listeners.addElement(listener);
	}
	
	/**
	 * Removes a TimeEntriesListener previously added with {@link #addTimeEntriesListener(TimeEntriesListener)}
	 * @param listener
	 */
	public void removeTimeEntriesListener(TimeEntriesListener listener){
		if(listeners.indexOf(listener) >= 0)
			listeners.removeElement(listener);
	}
	
	/**
	 * Call the all registered listeners, notifying them about the eventType 
	 * @param entry
	 * @param eventType can be: ADD_EVENT, REMOVE_EVENT, UPDATE_EVENT
	 */
	public void fireTimeEntryAdded(TimeEntry entry, String eventType){
		for(int i = 0; i < listeners.size(); i++){
			TimeEntriesListener listener = (TimeEntriesListener) listeners.elementAt(i);
			
			if(eventType == ADD_EVENT){			
				listener.timeEntryAdded(entry);
			}
			
			if(eventType == REMOVE_EVENT){
				listener.timeEntryDeleted(entry);
			}
			
			if(eventType == UPDATE_EVENT){
				listener.timeEntryUpdated(entry);
			}
		}
	}	
	
	/**
	 * Get shalow clone of the time entries vector
	 * @return
	 */
	public Vector getTimeEntries(){
		Vector clone = new Vector();
		for(int i = 0; i < timeEntries.size(); i++){
			clone.addElement(timeEntries.elementAt(i));
		}
		return timeEntries;
	}
	
	/**
	 * Get latest n tasks with logged time
	 * @param n - Maximum number of tasks to be returned
	 * @return - An array of tasks. An empty array in case of no time entries logged.
	 */
	public Task[] getLatestTasks(int n) {
		Vector latestTasks = new Vector();
		int i = timeEntries.size() - 1;
		while (latestTasks.size() < n && i >= 0) {
			TimeEntry timeEntry = (TimeEntry) timeEntries.elementAt(i);
			if (!latestTasks.contains(timeEntry.getTask())) {
				latestTasks.addElement(timeEntry.getTask());
			}
			i--;
		}
		Task[] tasks = new Task[latestTasks.size()];
		latestTasks.copyInto(tasks);
		return tasks;
	}

	public void removeTimeEntriesFor(Task task) {
		int i=0;
		while (i < timeEntries.size()) {
			TimeEntry entry = (TimeEntry) timeEntries.elementAt(i);
			if (entry.getTask() == task) {
				timeEntries.removeElementAt(i);
			} else {
				i++;
			}
		}
	}

	public void removeTimeEntriesFor(Project project) {
		int i=0;
		while (i < timeEntries.size()) {
			TimeEntry entry = (TimeEntry) timeEntries.elementAt(i);
			if (entry.getTask().getParent() == project) {
				timeEntries.removeElementAt(i);
			} else {
				i++;
			}
		}
	}
}
